import java.util.ArrayList;
import java.awt.image.BufferedImage;
import java.awt.image.RasterFormatException;
// these three for changing pixel colors.
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.AlphaComposite;

/**
 * Class made to hold a sequence of images that comprise an animation,
 * and to make each frame accessible.
 * @author Chris White
 *
 */
public class Sprite {

	final int image_width;
	final int image_height;
	
	private int totalAnimationLength = 0;
	private int currentFrame = 0;
	private Boolean animationEnded = false;
	ArrayList<BufferedImage> frames;
	/**
	 * The frame length is the number of frames a specific subimage in the sprite will be shown. for a five-subimage 
	 * sprite that spans 30 frames in a single rotation, frameLengths will determine how long each subimage is shown.  
	 */
	ArrayList<Integer> frameLengths;
	
	/**
	 * Takes the first frame of the animation, sets image_width and image_height
	 * @param image
	 */
	Sprite(BufferedImage image)
	{
		image_width = image.getWidth();
		image_height = image.getHeight();
		frames = new ArrayList<BufferedImage>();
		frames.add(image);
		frameLengths = new ArrayList<Integer>();
		frameLengths.add(1);
		totalAnimationLength = 1;
	}
	/**
	 * Takes the first frame of the animation, sets image_width and image_height
	 * @param image
	 */
	Sprite(BufferedImage image, int frame_length)
	{
		image_width = image.getWidth();
		image_height = image.getHeight();
		frames = new ArrayList<BufferedImage>();
		frames.add(image);
		frameLengths.add(frame_length);
		totalAnimationLength = frame_length;
	}
	
	/**
	 * Takes a sprite strip and manually cuts it into individual frames of 
	 * the indicated width and height. Will take num frames or until it reaches
	 * the edge of the strip. The strip is assumed to be oriented horizontally.
	 * |  1  |  2  |  3  |  4  |  5  |  etc.
	 * @param image
	 * @param width
	 * @param height
	 * @param num
	 */
	Sprite(BufferedImage imageStrip, int width, int height, int num)
	{
		image_width = width;
		image_height = height;
		frames = new ArrayList<BufferedImage>();
		frameLengths = new ArrayList<Integer>();
		
		int xbase = 0;
		int ybase = 0;
		
		try{
			while(num > 0)
			{
				frames.add(imageStrip.getSubimage(xbase, ybase, image_width, image_height));
				frameLengths.add(1); // add a length of 1 as default
				xbase += image_width; // go to the next frame over.
				num--; // this many frames left to grab
				totalAnimationLength += 1;
			}
		}
		catch(RasterFormatException RFE) // occurs if we go off the end of imageStrip
		{
			// just print the error; carry on.
			System.out.println("RasterFormatException while loading Sprite.\n\t-Carrying on");
//			RFE.printStackTrace();
		}
	}
	/**
	 * Same as directly above, but also takes an array of integers as frame lengths
	 * @param image
	 * @param width
	 * @param height
	 * @param num
	 */
	Sprite(BufferedImage imageStrip, int width, int height, int num, int[] lengths)
	{
//		imageStrip = makeColorInvisible(imageStrip, new Color(248, 56, 40));// TODO - this line for testing only
		image_width = width;
		image_height = height;		
		frames = new ArrayList<BufferedImage>();
		frameLengths = new ArrayList<Integer>();
		
		int xbase = 0;
		int ybase = 0;
		
		try{
			int index = 0;
			while(index < num)
			{
				frames.add(imageStrip.getSubimage(xbase, ybase, image_width, image_height));
				if(lengths.length > index) // ensure that lengths has enough integers
				{

					dbg("Adding a frame with length " + lengths[index]);
					frameLengths.add(lengths[index]); // if so, apply it for the frame. We are trusting the user 
					totalAnimationLength += lengths[index];  // not to give us negative or zero values.
				}
				else
				{
					dbg("Adding a frame with length " + 1);
					frameLengths.add(1); // if lengths has too few values, use 1 as the default.
					totalAnimationLength += 1;
				}
				xbase += image_width; // go to the next frame over.
				index++; // num - index frames left to grab
			}
		}
		catch(RasterFormatException RFE) // occurs if we go off the end of imageStrip
		{
			// just print the error, carry on.
			RFE.printStackTrace();
		}
		dbg("Sprite has " + frames.size() + " frames");
		dbg(frameLengths.toString());
	}
	
	/**
	 * Add an image to the animation. Must have the same dimensions as the first frame.
	 * @param image
	 * @return True if the image was added successfully, false else
	 */
	public Boolean addFrame(BufferedImage image)
	{
		// if the image has the correct dimensions, add it to the sprite
		if(image.getWidth() == image_width && image.getHeight() == image_height)
		{
			frames.add(image);
			frameLengths.add(1); // default length
			return true;
		}
		else return false;
	}
	
	/**
	 * Same as the directly above, but also takes the frame Length of the subimage being added
	 * @param image
	 * @return True if the image was added successfully, false else
	 */
	public Boolean addFrame(BufferedImage image, int frame_length)
	{
		// if the image has the correct dimensions, add it to the sprite
		if(image.getWidth() == image_width && image.getHeight() == image_height)
		{
			frames.add(image);
			frameLengths.add(frame_length);
			return true;
		}
		else return false;
	}
	
	/**
	 * Ensure the index is within range (treat it as wrapping if not) and 
	 * return the correct frame, accounting for frames with different lengths
	 * (e.g. for a one-frame sprite, any index will return the same frame)
	 * @param frame
	 * @return
	 */
	public BufferedImage getFrame(int frame)
	{
		// make sure the desired frame falls within the animation range.
		while(frame >= totalAnimationLength)
		{
			frame -= totalAnimationLength;
		}
		while(frame < 0)
		{
			frame += totalAnimationLength;
		}
		
		// now, figure out which actual frame is being referenced.
		int index = 0;
		while(frame - frameLengths.get(index) >= 0)
		{
			frame -= frameLengths.get(index);
			index++;
		}
		return frames.get(index);
	}
	
	/**
	 * Returns whether or not the last call to getNextFrame() returned the final frame in the animation.
	 * @return
	 */
	public Boolean animationEnded()
	{
		return animationEnded;
	}
	
	/**
	 * resets the animation to begin again from the beginning.
	 */
	public void resetAnimation()
	{
		currentFrame = 0;
		animationEnded = false;
	}
	/**
	 * resets the animation to begin from the point indicated by index
	 * @param index
	 */
	public void resetAnimation(int index)
	{
		currentFrame = index;
		animationEnded = false;
	}
	
	/**
	 * Much like getFrame(), but uses an internal counter to automatically return the next frame in the sequence. 
	 * This also automatically sets animationEnded() whenever the final frame is returned. Will continue to return 
	 * the final (first?) frame in the animation until resetAnimation() is called. 
	 * @return
	 */
	public BufferedImage getNextFrame()
	{
		if(animationEnded) // just return the first frame since the animation is over.
			return getFrame(0);
		
		// else, find/return the next frame.
		currentFrame += 1;
		// first, make sure 0 <= currentFrame < totalAnimationLength
		if(currentFrame >= totalAnimationLength)
		{
			currentFrame -= totalAnimationLength;
		}
		// check if we are now at the last frame, set flag if the animation has ended.
		if(currentFrame == totalAnimationLength-1)
		{
			animationEnded = true;
		}
		return getFrame(currentFrame);
	}
	
	/**
	 * returns the index of the last frame returned by getNextFrame()
	 * @return
	 */
	public int getCurrentFrame()
	{
		return currentFrame;
	}
	
	/**
	 * returns the total number of frames the Sprite has, accounting for frameLengths
	 * @return
	 */
	public int getTotalFrames(){
		return totalAnimationLength;
	}
	
	public BufferedImage makeColorInvisible(BufferedImage image, Color color) {
		// 248, 56, 40   #F83828   <-- red color.
		// 88, 152, 248  #5898F8   <-- blue color
		// invisible: 0x8F1C1C
		
		//BufferedImage image = loadImage(ref);
		BufferedImage dimg = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);

		Graphics2D g = dimg.createGraphics();
		g.setComposite(AlphaComposite.Src);
		g.drawImage(image, null, 0, 0);
		g.dispose();
		for(int i = 0; i < dimg.getHeight(); i++) {
			for(int j = 0; j < dimg.getWidth(); j++) {
				if(dimg.getRGB(j, i) == color.getRGB()) {
				dimg.setRGB(j, i, 0x8F1C1C);
				}
			}
		}
		return dimg;
	}
	
	private void dbg(String msg)
	{
		System.out.println(msg);
	}
}